// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modload

// 此文件包含模块模式包加载器以及一些附件
// 与包导入图相关的函数。
// None
// 有两个导出到包加载的入口点-LoadPackages和
// ImportFromFiles—这两个文件都是根据loadFromRoots实现的，而loadFromRoots本身
// 操纵加载器结构的实例。
// None
// 尽管大多数加载状态都保存在loader结构中，
// 一个关键部分——构建列表——是一个全局列表，因此可以对其进行修改
// 与加载操作分离，例如在“go-get”期间
// 升级/降级或“go mod”操作。
// TODO（#40775）：让加载器接收并返回可能会很好
// 一个构建列表，而不是硬编码使用全局。
// None
// 加载是一个迭代过程。在每次迭代中，我们尝试加载
// 请求的包及其可传递导入，然后尝试解析模块
// 对于仍然丢失的任何导入包。
// None
// 每个迭代的第一步确定一组“根”包。
// 通常根包与命名模式完全匹配
// 论据。然而，对于“all”元模式，最后一组包是
// 从包导入图计算，因此不能是初始值
// 加载该图形的输入。相反，“all”模式的根包
// 是包含在主模块中的，以及
// 加载器指示它将这些根动态扩展到完整的“全部”
// 加载过程中的模式。
// None
// 每个loadPkg实例上的pkgInAll标志跟踪
// 已知包与“all”元模式匹配。
// 在以下情况下，包与“全部”模式匹配：
// -它位于主模块中，或
// -通过主模块中的任何测试导入，或
// -由另一个“全部”包装进口，或
// -主模块指定go版本≤ 1.15，包装为进口包装
// 通过对“all”中的另一个包进行*测试。
// None
// 当我们实现延迟加载时，我们将记录提供包的模块
// 在“all”中，即使我们只加载单个包，也会设置
// pkgInAll标志，而不管“all”模式是否为根。
// （这对于维护中所述的“导入不变量”是必要的
// https:
// None
// 因为“go mod vendor”删减了对供应商软件包的测试
// Go 1.11–1.15中带-mod=vendor的“all”模式的行为是相同的
// 作为1.16+中的“全部”模式（不考虑-mod标志）。
// 加载器使用GoVersion参数确定“所有”
// 模式应结束测试（如Go 1.11–1.15中所述）或仅在测试结束时停止
// 由主模块中的包和测试传递导入的包
// （“Go 1.16+中的全部”和Go 1.11+中的“Go mod供应商”。
// None
// 请注意，即使在我们
// 正在加载“全部”模式。例如，可传递的包
// 必须加载命令行上命名的其他根的依赖项，但它们是
// 不是全部。（mod_notall测试说明了这种行为。）
// 类似地，如果设置了LoadTests标志，但“all”模式未关闭
// 过度测试依赖性，然后当我们加载
// “全部”，但在主模块之外，该测试的依赖项不会
// 他们自己必然在“所有”中。（Go中不会出现该配置
// 1.11–1.15，但在Go 1.16+中是可能的。）
// None
// 加载从根开始，使用限制为
// 活动工作量（以避免磁盘、CPU内核和/或磁盘饱和）
// 网络连接）。每个包在第一次添加到队列时都会被添加到队列中
// 由另一个包导入。当我们完成确认进口的
// 一个包，如果需要，我们为该包添加测试。考试可能会被取消
// 在下列情况下需要：
// -包与根模式匹配，并且请求对根进行测试，或者
// -包位于主模块中，请求“all”模式
// （因为“all”模式包括主模式中测试的依赖关系
// 模块），或
// -该包位于“all”中，我们使用的“all”的定义包括
// 测试的依赖性（如Go中的情况）≤1.15).
// None
// 在加载所有可用的包之后，我们将检查结果以
// 识别仍然丢失的任何请求或导入的包，如果
// 那么，我们可以将哪些模块添加到模块图中，以使
// 缺少可用的软件包。我们将其添加到模块图中并进行迭代，
// 直到所有包成功解析或我们无法识别任何包
// 解决任何剩余丢失包的模块。
// None
// 如果主模块是“整洁的”（也就是说，如果“go mod tidy”是不可操作的）
// 并且所有请求的包都在“all”中，然后在单个
// 迭代。
// TODO（bcmills）：如果
// 不管怎样，请求的包都来自于自身整洁的模块
// 这些包是否在“全部”中。今天，这需要两次迭代
// 如果在主模块的现有依赖项中找不到这些包。

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"go/build"
	"io/fs"
	"os"
	"path"
	pathpkg "path"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync"
	"sync/atomic"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/imports"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/mvs"
	"cmd/go/internal/par"
	"cmd/go/internal/search"
	"cmd/go/internal/str"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

// loaded是最近使用的包加载器。
// 它包含关于单个包的详细信息。
// None
// 此变量只能在顶级导出中直接访问
// 功能。需要或产生*加载器的所有其他函数都应通过
// 或者将其作为显式参数返回。
var loaded *loader

// PackageOpts控制LoadPackages函数的行为。
type PackageOpts struct {
	// GoVersion是Go.mod文件应更新到的Go版本
	// 在加载包之后。
	// None
	// 空的GoVersion表示使用已在中指定的Go版本
	// 主模块的go.mod文件，如果没有主模块，则为最新的go版本
	// 单元
	GoVersion string

	// 标记是有效的构建标记（由
	// cmd/go/internal/imports包）。
	// 如果为nil，则视为等同于imports.Tags（）。
	Tags map[string]bool

	// Tidy（如果为true）请求将生成列表和go.sum文件缩减为
	// 重新加载请求的数据所需的最小依赖关系
	// 包装。
	Tidy bool

	// TidyCompatibleVersion是必须能够
	// 重新加载请求的包。
	// None
	// 如果为空，则兼容版本为紧靠
	// go.mod文件中列出的“go”版本。
	TidyCompatibleVersion string

	// VendorModulesIngootSrc表示如果我们在
	// GOROOT/src，模块供应商目录中的包应解析为
	// 实际模块依赖项（而不是标准库包）。
	VendorModulesInGOROOTSrc bool

	// ResolveMissingImports指示我们应该尝试添加模块
	// 解析未找到的包的导入所需的依赖项。
	// None
	// 对于支持-mod标志的命令，解析导入仍然可能失败
	// 如果该标志设置为“只读”（默认）或“供应商”。
	ResolveMissingImports bool

	// AssumeRootsPorted表示根的可传递依赖项
	// 包装应被视为这些根将由主要供应商进口
	// 单元
	AssumeRootsImported bool

	// 如果AllowPackage不是nil，则在识别提供
	// 每个包裹。如果AllowPackage返回非nil错误，则设置该错误
	// 对于该包，将不进行该包的导入和测试
	// 加载。
	// None
	// AllowPackage可由多个Goroutine并发调用，
	// 对于给定的包路径，可以多次调用和。
	AllowPackage func(ctx context.Context, path string, mod module.Version) error

	// LoadTests加载与请求的测试匹配的每个包的测试依赖项
	// 图案如果ResolveMissingImports也为true，则测试依赖项将为
	// 如果丢失，则已解决。
	LoadTests bool

	// UseVendorAll使“all”包模式被解释为
	// 运行“go mod vendor”（或使用“-mod=vendor”构建）。
	// None
	// 对于声明为“go 1.16”或更高版本的模块，这是一个不可操作的选项，对此
	// 是模块模式中“所有”模式的默认（也是唯一）解释。
	UseVendorAll bool

	// AllowErrors表示如果发生以下情况，LoadPackages不应终止进程
	// 出现错误。
	AllowErrors bool

	// 沉默打包错误指示LoadPackages不应打印错误
	// 在匹配或加载包时发生，不应终止
	// 如果发生此类错误，则处理。
	// None
	// 模块图中遇到的错误仍将报告。
	// None
	// 调用者可以使用查找来检索沉默的包错误
	// 函数的Errs字段中仍填充匹配错误
	// 关联搜索。匹配。）
	SilencePackageErrors bool

	// SilenceMissingStdImports表示不应打印LoadPackages
	// 如果缺少导入的包，则返回错误或终止进程，并且
	// 导入路径看起来可能在标准库中（可能在
	// 未来版本）。
	SilenceMissingStdImports bool

	// SilenceNoGoers错误表示不应打印LoadPackages
	// imports.ErrNoGo错误。
	// 这允许调用方调用LoadPackages（并报告其他错误）
	// 不知道给定标记的请求包是否存在。
	// None
	// 请注意，如果请求的包根本不存在*，它将失败
	// 在模块解析期间，错误将不会被抑制。
	SilenceNoGoErrors bool

	// 消声匹配消声抑制正常发出的警告
	// 与任何包都不匹配的模式。
	SilenceUnmatchedWarnings bool
}

// LoadPackages标识与给定模式和模式匹配的包集
// 加载以该集合为根的导入图中的包。
func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (matches []*search.Match, loadedPackages []string) {
	if opts.Tags == nil {
		opts.Tags = imports.Tags()
	}

	patterns = search.CleanPatterns(patterns)
	matches = make([]*search.Match, 0, len(patterns))
	allPatternIsRoot := false
	for _, pattern := range patterns {
		matches = append(matches, search.NewMatch(pattern))
		if pattern == "all" {
			allPatternIsRoot = true
		}
	}

	updateMatches := func(rs *Requirements, ld *loader) {
		for _, m := range matches {
			switch {
			case m.IsLocal():
				// 在第一次迭代时评估文件系统目录列表。
				if m.Dirs == nil {
					matchLocalDirs(ctx, m, rs)
				}

				// 制作目录列表的副本并转换为导入路径。
				// 请注意，目录是否对应于导入路径
				// 随着生成列表的更新而更改，并且目录可以更改
				// 从不在构建列表中到在构建列表中，然后作为
				// 特定模块的确切版本在运行期间会增加
				// 加载程序的迭代。
				m.Pkgs = m.Pkgs[:0]
				for _, dir := range m.Dirs {
					pkg, err := resolveLocalPackage(ctx, dir, rs)
					if err != nil {
						if !m.IsLiteral() && (err == errPkgIsBuiltin || err == errPkgIsGorootSrc) {
							continue // 不要在通配符模式中包含“内置”或GOROOT/src。
						}

						// 如果我们在模块外部，请确保故障模式
						// 表明。
						ModRoot()

						if ld != nil {
							m.AddError(err)
						}
						continue
					}
					m.Pkgs = append(m.Pkgs, pkg)
				}

			case m.IsLiteral():
				m.Pkgs = []string{m.Pattern()}

			case strings.Contains(m.Pattern(), "..."):
				m.Errs = m.Errs[:0]
				mg, err := rs.Graph(ctx)
				if err != nil {
					// 模块图不完整（或可能不完整）-可能我们未能完成
					// 加载某些模块的需求。这是一个匹配错误
					// 将模式添加到包中，因为我们可能缺少一些包
					// 或者我们可能会错误地匹配错误版本的
					// 模块。但是，对于“go list-e”这样的情况，不应出现错误
					// 一定会阻止我们装载我们能找到的包裹。
					m.Errs = append(m.Errs, err)
				}
				matchPackages(ctx, m, opts.Tags, includeStd, mg.BuildList())

			case m.Pattern() == "all":
				if ld == nil {
					// 初始根是主模块中的包。
					// loadFromRoots将其扩展为“all”。
					m.Errs = m.Errs[:0]
					matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target})
				} else {
					// 从主模块中的包开始，
					// 列举“全部”的完整列表。
					m.Pkgs = ld.computePatternAll()
				}

			case m.Pattern() == "std" || m.Pattern() == "cmd":
				if m.Pkgs == nil {
					m.MatchPackages() // 在GOROOT/src中找到包。
				}

			default:
				panic(fmt.Sprintf("internal error: modload missing case for pattern %s", m.Pattern()))
			}
		}
	}

	initialRS, _ := loadModFile(ctx) // 忽略needCommit——不管怎样，我们最终都会提交。

	ld := loadFromRoots(ctx, loaderParams{
		PackageOpts:  opts,
		requirements: initialRS,

		allPatternIsRoot: allPatternIsRoot,

		listRoots: func(rs *Requirements) (roots []string) {
			updateMatches(rs, nil)
			for _, m := range matches {
				roots = append(roots, m.Pkgs...)
			}
			return roots
		},
	})

	// 最后一次完成通配符。
	updateMatches(ld.requirements, ld)

	// 列出匹配模式中的错误（例如目录权限
	// 通配符模式错误）。
	if !ld.SilencePackageErrors {
		for _, match := range matches {
			for _, err := range match.Errs {
				ld.errorf("%v\n", err)
			}
		}
	}
	base.ExitIfErrors()

	if !opts.SilenceUnmatchedWarnings {
		search.WarnUnmatched(matches)
	}

	if opts.Tidy {
		if cfg.BuildV {
			mg, _ := ld.requirements.Graph(ctx)

			for _, m := range initialRS.rootModules {
				var unused bool
				if ld.requirements.depth == eager {
					// 如果m完全从模块图中删除，则m未使用。如果是
					// 仅从直接降级为间接，它可能仍在通过使用
					// 可传递的导入。
					unused = mg.Selected(m.Path) == "none"
				} else {
					// 如果从根部掉落，则m未使用。如果它仍然存在
					// 作为可传递依赖项，不需要该可传递依赖项
					// 通过主模块中的任何包或测试。
					_, ok := ld.requirements.rootSelected(m.Path)
					unused = !ok
				}
				if unused {
					fmt.Fprintf(os.Stderr, "unused %s\n", m.Path)
				}
			}
		}

		keep := keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly)
		if compatDepth := modDepthFromGoVersion(ld.TidyCompatibleVersion); compatDepth != ld.requirements.depth {
			compatRS := newRequirements(compatDepth, ld.requirements.rootModules, ld.requirements.direct)
			ld.checkTidyCompatibility(ctx, compatRS)

			for m := range keepSums(ctx, ld, compatRS, loadedZipSumsOnly) {
				keep[m] = true
			}
		}

		if allowWriteGoMod {
			modfetch.TrimGoSum(keep)

			// 下面的committerequirement也将调用WriteGoSum，但是“keep”映射
			// 我们这里有更大的承诺：承诺要求只承诺
			// loaded.requirements，但在这里我们可能也加载了（并希望
			// 保留COMPATR中其他实体的校验和，这些实体是
			// 仅在与ld.TidyCompatibleVersion兼容时需要。
			modfetch.WriteGoSum(keep)
		}
	}

	// 成功更新go.mod和go.sum（如果需要）并返回结果。
	loaded = ld
	commitRequirements(ctx, loaded.GoVersion, loaded.requirements)

	for _, pkg := range ld.pkgs {
		if !pkg.isTest() {
			loadedPackages = append(loadedPackages, pkg.path)
		}
	}
	sort.Strings(loadedPackages)
	return matches, loadedPackages
}

// matchLocalDirs类似于m.MatchDirs，但尝试避免扫描目录
// 在标准库和活动模块之外。
func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
	if !m.IsLocal() {
		panic(fmt.Sprintf("internal error: resolveLocalDirs on non-local pattern %s", m.Pattern()))
	}

	if i := strings.Index(m.Pattern(), "..."); i >= 0 {
		// 模式是本地的，但它是一个通配符。其一揽子计划将
		// 仅当路径在标准范围内时才解析为路径
		// 库、主模块或主模块的某些依赖项
		// 单元在遍历文件系统之前，请验证：文件系统
		// 在/var或/etc这样的目录中漫游可能非常昂贵！
		dir := filepath.Dir(filepath.Clean(m.Pattern()[:i+3]))
		absDir := dir
		if !filepath.IsAbs(dir) {
			absDir = filepath.Join(base.Cwd(), dir)
		}
		if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
			m.Dirs = []string{}
			m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
			return
		}
	}

	m.MatchDirs()
}

// resolveLocalPackage将文件系统路径解析为包路径。
func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (string, error) {
	var absDir string
	if filepath.IsAbs(dir) {
		absDir = filepath.Clean(dir)
	} else {
		absDir = filepath.Join(base.Cwd(), dir)
	}

	bp, err := cfg.BuildContext.ImportDir(absDir, 0)
	if err != nil && (bp == nil || len(bp.IgnoredGoFiles) == 0) {
		// golang.org/issue/32917：我们应该解析到
		// 仅当相对路径实际包含代码时才使用包路径
		// 为了那个包裹。
		// None
		// 如果命名目录不存在或不包含Go文件，
		// 包不存在。
		// 其他错误可能会影响包加载，但不会影响解析。
		if _, err := fsys.Stat(absDir); err != nil {
			if os.IsNotExist(err) {
				// 将特定于操作系统的错误规范化为errDirectoryNotFound，以便
				// 用户可以更轻松地搜索消息。
				return "", &fs.PathError{Op: "stat", Path: absDir, Err: errDirectoryNotFound}
			}
			return "", err
		}
		if _, noGo := err.(*build.NoGoError); noGo {
			// 一个不包含任何Go源文件的目录-甚至被忽略
			// 一个！-不是Go包，我们无法将其解析为包
			// 路径，因为该路径可能由其他路径提供
			// 单元
			// None
			// 任何其他错误都表明包“存在”（至少在
			// 感觉到它不能存在于任何其他模块中），但有一些其他功能
			// 问题（例如语法错误）。
			return "", err
		}
	}

	if modRoot != "" && absDir == modRoot {
		if absDir == cfg.GOROOTsrc {
			return "", errPkgIsGorootSrc
		}
		return targetPrefix, nil
	}

	// 注意：此处对@的检查只是为了避免误解
	// 模块缓存目录（以前是GOPATH/src/mod/foo@v1.5.2/酒吧）。
	// 严格说来，这不是必要的，但有助于保持检查。
	if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
		suffix := filepath.ToSlash(absDir[len(modRoot):])
		if strings.HasPrefix(suffix, "/vendor/") {
			if cfg.BuildMod != "vendor" {
				return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
			}

			readVendorList()
			pkg := strings.TrimPrefix(suffix, "/vendor/")
			if _, ok := vendorPkgModule[pkg]; !ok {
				return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
			}
			return pkg, nil
		}

		if targetPrefix == "" {
			pkg := strings.TrimPrefix(suffix, "/")
			if pkg == "builtin" {
				// “builtin”是一个带有真实源文件的伪包。
				// 它不包括在“std”中，所以它不应该从“”解析
				// 在模块“std”内。
				return "", errPkgIsBuiltin
			}
			return pkg, nil
		}

		pkg := targetPrefix + suffix
		if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
			return "", err
		} else if !ok {
			return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
		}
		return pkg, nil
	}

	if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
		pkg := filepath.ToSlash(sub)
		if pkg == "builtin" {
			return "", errPkgIsBuiltin
		}
		return pkg, nil
	}

	pkg := pathInModuleCache(ctx, absDir, rs)
	if pkg == "" {
		return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir))
	}
	return pkg, nil
}

var (
	errDirectoryNotFound = errors.New("directory not found")
	errPkgIsGorootSrc    = errors.New("GOROOT/src is not an importable package")
	errPkgIsBuiltin      = errors.New(`"builtin" is a pseudo-package, not an importable package`)
)

// pathInModuleCache返回目录dir的导入路径，
// 如果dir位于生成列表中模块的模块缓存副本中。
func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string {
	tryMod := func(m module.Version) (string, bool) {
		var root string
		var err error
		if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
			root = repl.Path
			if !filepath.IsAbs(root) {
				root = filepath.Join(ModRoot(), root)
			}
		} else if repl.Path != "" {
			root, err = modfetch.DownloadDir(repl)
		} else {
			root, err = modfetch.DownloadDir(m)
		}
		if err != nil {
			return "", false
		}

		sub := search.InDir(dir, root)
		if sub == "" {
			return "", false
		}
		sub = filepath.ToSlash(sub)
		if strings.Contains(sub, "/vendor/") || strings.HasPrefix(sub, "vendor/") || strings.Contains(sub, "@") {
			return "", false
		}

		return path.Join(m.Path, filepath.ToSlash(sub)), true
	}

	if rs.depth == lazy {
		for _, m := range rs.rootModules {
			if v, _ := rs.rootSelected(m.Path); v != m.Version {
				continue // m是根，但对于同一路径，我们有一个更高的根。
			}
			if importPath, ok := tryMod(m); ok {
				// CheckMultiplePath确保一个模块最多可用于一个
				// 需求，所以这必须是它。
				return importPath
			}
		}
	}

	// 没有一个根包含dir，或者我们处于急切模式，希望加载
	// 完整的模块图更具攻击性。无论哪种方式，请检查完整的图表
	// 查看目录是否为非根依赖项。
	// None
	// 如果根与完整模块图不一致，则选择
	// 根模块的版本可能不同于我们上面已经检查过的版本。
	// 再次检查这些路径。

	mg, _ := rs.Graph(ctx)
	var importPath string
	for _, m := range mg.BuildList() {
		var found bool
		importPath, found = tryMod(m)
		if found {
			break
		}
	}
	return importPath
}

// ImportFromFiles根据需要将模块添加到生成列表中
// 以满足命名Go源文件中的导入。
// None
// 缺少依赖项中的错误将被沉默。
// None
// TODO（bcmills）：沉默错误似乎已关闭。仔细看看这个，然后
// 找出错误报告实际上应该是什么。
func ImportFromFiles(ctx context.Context, gofiles []string) {
	rs := LoadModFile(ctx)

	tags := imports.Tags()
	imports, testImports, err := imports.ScanFiles(gofiles, tags)
	if err != nil {
		base.Fatalf("go: %v", err)
	}

	loaded = loadFromRoots(ctx, loaderParams{
		PackageOpts: PackageOpts{
			Tags:                  tags,
			ResolveMissingImports: true,
			SilencePackageErrors:  true,
		},
		requirements: rs,
		listRoots: func(*Requirements) (roots []string) {
			roots = append(roots, imports...)
			roots = append(roots, testImports...)
			return roots
		},
	})
	commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
}

// DirImportPath返回dir的有效导入路径，
// 前提是它位于主模块内，否则返回“”。
func DirImportPath(ctx context.Context, dir string) string {
	if !HasModRoot() {
		return "."
	}
	LoadModFile(ctx) // 设置targetPrefix。

	if !filepath.IsAbs(dir) {
		dir = filepath.Join(base.Cwd(), dir)
	} else {
		dir = filepath.Clean(dir)
	}

	if dir == modRoot {
		return targetPrefix
	}
	if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
		suffix := filepath.ToSlash(dir[len(modRoot):])
		if strings.HasPrefix(suffix, "/vendor/") {
			return strings.TrimPrefix(suffix, "/vendor/")
		}
		return targetPrefix + suffix
	}
	return "."
}

// ImportMap返回实际的包导入路径
// 用于在源代码中找到的导入路径。
// 如果给定的导入路径没有出现在源代码中
// 对于已加载的包，ImportMap返回空字符串。
func ImportMap(path string) string {
	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
	if !ok {
		return ""
	}
	return pkg.path
}

// PackageDir返回包含源代码的目录
// 对于由导入路径命名的包。
func PackageDir(path string) string {
	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
	if !ok {
		return ""
	}
	return pkg.dir
}

// PackageModule返回提供由导入路径命名的包的模块。
func PackageModule(path string) module.Version {
	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
	if !ok {
		return module.Version{}
	}
	return pkg.mod
}

// Lookup返回源目录、导入路径和任何加载错误
// 从parentDir中的包导入的路径处的包。
// 查找要求此包中的某个加载函数已经
// 有人打电话来。
func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath string, err error) {
	if path == "" {
		panic("Lookup called with empty package path")
	}

	if parentIsStd {
		path = loaded.stdVendor(parentPath, path)
	}
	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
	if !ok {
		// 加载程序应该已经找到所有相关路径。
		// 但也有一些例外情况：
		// -在不带-test的go list期间，p.Resolve调用处理p.testimport和p.xtestimport
		// 在这里结束以规范化导入路径。
		// -在任何加载过程中，像“不安全”这样的未加载包都会在这里结束。
		// -在任何加载过程中，构建注入依赖项（如“runtime/cgo”）都会在这里结束。
		// -因为我们忽略了模块加载器中的appengine/*，
		// 任何实际appengine/*库的依赖项都在这里结束。
		dir := findStandardImportPath(path)
		if dir != "" {
			return dir, path, nil
		}
		return "", "", errMissing
	}
	return pkg.dir, pkg.path, pkg.err
}

// 加载程序管理加载有关的信息的过程
// 特定构建所需的包，
// 检查模块集中的软件包是否可用，
// 以及在需要时更新模块集。
type loader struct {
	loaderParams

	// AllCloseSoverts表示“all”模式是否包括
	// 主模块外测试的相关性（如Go 1.11–1.15）。
	// （否则，与Go 1.16+一样，“所有”模式仅包括包
	// 可传递地*由*主模块中的包和测试导入。）
	allClosesOverTests bool

	work *par.Queue

	// 每次迭代时重置
	roots    []*loadPkg
	pkgCache *par.Cache // 包路径（字符串）→ *装入包装
	pkgs     []*loadPkg // 加载包和测试的传递性关闭；在构建堆栈中填充
}

// loaderParams配置加载的包以及报告的属性
// 通过，加载程序实例。
type loaderParams struct {
	PackageOpts
	requirements *Requirements

	allPatternIsRoot bool // “全部”模式是附加根吗？

	listRoots func(rs *Requirements) []string
}

func (ld *loader) reset() {
	select {
	case <-ld.work.Idle():
	default:
		panic("loader.reset when not idle")
	}

	ld.roots = nil
	ld.pkgCache = new(par.Cache)
	ld.pkgs = nil
}

// errorf通过os.Stderr或base.errorf报告错误，
// 根据是否设置了ld.AllowErrors。
func (ld *loader) errorf(format string, args ...interface{}) {
	if ld.AllowErrors {
		fmt.Fprintf(os.Stderr, format, args...)
	} else {
		base.Errorf(format, args...)
	}
}

// loadPkg记录有关单个加载包的信息。
type loadPkg struct {
	// 在施工时填充：
	path   string // 导入路径
	testOf *loadPkg

	// 在构建时填充，并由（*loader）更新。applyPkgFlags:
	flags atomicLoadPkgFlags

	// 由（*加载器）填充。加载：
	mod         module.Version // 模块提供包
	dir         string         // 包含源代码的目录
	err         error          // 加载包时出错
	imports     []*loadPkg     // 这个人进口的包裹
	testImports []string       // 仅测试导入，保存以供pkg.test使用。
	inStd       bool

	// 由（*loader）填充。pkgTest:
	testOnce sync.Once
	test     *loadPkg

	// 由（*loader）中的后处理填充。构建堆栈：
	stack *loadPkg // 包导入此包的最小导入堆栈中的此包
}

// loadPkgFlags是一组标记，用于跟踪有关包的元数据。
type loadPkgFlags int8

const (
	// pkgInAll表示包处于“all”包模式，
	// 不管我们是否正在加载“all”包模式。
	// None
	// 如果同时设置了pkgInAll标志和pkgImportsLoaded标志，则调用方
	// 设置最后一个标志的人必须将pkgInAll标记传播给所有人
	// 已标记包装的进口货物。
	// None
	// 如果某个测试会提升它所支持的包，则该测试将标记为pkgInAll
	// 导入为“全部”（例如，当测试本身在主
	// 模块，或者当ld.allClosesOverTests为true时）。
	pkgInAll loadPkgFlags = 1 << iota

	// pkgIsRoot表示包与根包之一匹配
	// 调用方请求的模式。
	// None
	// 如果设置了LoadTests，则在同时设置pkgIsRoot和pkgImportsLoaded时，
	// 设置最后一个标志的调用方必须为
	// 包（在pkg.test字段中）。
	// None
	// 如果“all”模式包含在根目录中，则“all”中包含非测试包
	// 也是根（并且必须标记为pkgIsRoot）。
	pkgIsRoot

	// pkgFromRoot表示包处于的传递闭包中
	// 从根开始导入。（请注意，每个标记为pkgIsRoot的包
	// 也被简单地标记为pkgFromRoot。）
	pkgFromRoot

	// pkgImportsLoaded表示
	// loadPkg已填充。
	pkgImportsLoaded
)

// 报告cond中的所有标志是否在f中设置。
func (f loadPkgFlags) has(cond loadPkgFlags) bool {
	return f&cond == cond
}

// atomicLoadPkgFlags存储一个loadPkgFlags，可以为其指定各个标志
// 原子添加。
type atomicLoadPkgFlags struct {
	bits int32
}

// 更新设置af中的给定标志（除了已设置的任何标志）。
// None
// update返回以前的标志状态，以便调用方可以确定
// 旗帜是新设的。
func (af *atomicLoadPkgFlags) update(flags loadPkgFlags) (old loadPkgFlags) {
	for {
		old := atomic.LoadInt32(&af.bits)
		new := old | int32(flags)
		if new == old || atomic.CompareAndSwapInt32(&af.bits, old, new) {
			return loadPkgFlags(old)
		}
	}
}

// 报告是否在af中设置了cond中的所有标志。
func (af *atomicLoadPkgFlags) has(cond loadPkgFlags) bool {
	return loadPkgFlags(atomic.LoadInt32(&af.bits))&cond == cond
}

// isTest报告pkg是否是另一个包的测试。
func (pkg *loadPkg) isTest() bool {
	return pkg.testOf != nil
}

// fromExternalModule报告pkg是否是从除
// 主模块。
func (pkg *loadPkg) fromExternalModule() bool {
	if pkg.mod.Path == "" {
		return false // 从标准库而不是模块加载
	}
	if pkg.mod.Path == Target.Path {
		return false // 从主模块加载。
	}
	return true
}

var errMissing = errors.New("cannot find package")

// loadFromRoots尝试加载处理一组数据所需的构建图
// 根包及其依赖项。
// None
// 根包集由params.listRoots函数返回，并且
// 通过跟踪导入（可能还有测试）扩展到完整的包集
// 根据需要。
func loadFromRoots(ctx context.Context, params loaderParams) *loader {
	ld := &loader{
		loaderParams: params,
		work:         par.NewQueue(runtime.GOMAXPROCS(0)),
	}

	if ld.GoVersion == "" {
		ld.GoVersion = modFileGoVersion()

		if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
			ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
			base.ExitIfErrors()
		}
	}

	if ld.Tidy {
		if ld.TidyCompatibleVersion == "" {
			ld.TidyCompatibleVersion = priorGoVersion(ld.GoVersion)
		} else if semver.Compare("v"+ld.TidyCompatibleVersion, "v"+ld.GoVersion) > 0 {
			// Go工具链的每个版本都知道如何解释Go.mod和
			// 由所有以前版本生成的go.sum文件，因此具有兼容性
			// 高于go.mod版本的版本不会添加任何内容。
			ld.TidyCompatibleVersion = ld.GoVersion
		}
	}

	if semver.Compare("v"+ld.GoVersion, narrowAllVersionV) < 0 && !ld.UseVendorAll {
		// 模块的go版本明显早于对lazy的“all”更改
		// 加载，因此继续使用旧的解释。
		ld.allClosesOverTests = true
	}

	var err error
	ld.requirements, err = convertDepth(ctx, ld.requirements, modDepthFromGoVersion(ld.GoVersion))
	if err != nil {
		ld.errorf("go: %v\n", err)
	}

	if ld.requirements.depth == eager {
		var err error
		ld.requirements, _, err = expandGraph(ctx, ld.requirements)
		if err != nil {
			ld.errorf("go: %v\n", err)
		}
	}

	for {
		ld.reset()

		// 加载根包及其导入。
		// 注意：返回的根可以在每次迭代中更改，
		// 因为包模式的扩展取决于
		// 我们正在使用的构建列表。
		rootPkgs := ld.listRoots(ld.requirements)

		if ld.requirements.depth == lazy && cfg.BuildMod == "mod" {
			// 在开始加载包的可传递导入之前，找到所有
			// 根包并将其包含的模块升级到根模块
			// 依赖关系。如果他们的go.mod文件整洁（常见情况），并且
			// 根包集不变，然后我们可以选择正确的
			// 首次尝试并完成所有可传递导入的版本
			// 在单个迭代中加载。
			changedBuildList := ld.preloadRootModules(ctx, rootPkgs)
			if changedBuildList {
				// 生成列表已更改，因此根包集可能也已更改
				// 改变。重新开始以获取更改。（对树根进行预压非常重要
				// 比加载完整导入图更便宜，因此我们宁愿付费
				// 进行额外的预加载迭代，而不是最终
				// 丢弃加载的完整迭代的结果。）
				continue
			}
		}

		inRoots := map[*loadPkg]bool{}
		for _, path := range rootPkgs {
			root := ld.pkg(ctx, path, pkgIsRoot)
			if !inRoots[root] {
				ld.roots = append(ld.roots, root)
				inRoots[root] = true
			}
		}

		// ld.pkg将导入的包添加到工作队列，并调用applyPkgFlags，
		// 根据需要添加测试（和测试依赖项）。
		// None
		// 当队列中的所有工作完成后，我们将知道
		// 已加载依赖项的可传递闭包。
		<-ld.work.Idle()

		ld.buildStacks()

		changed, err := ld.updateRequirements(ctx)
		if err != nil {
			ld.errorf("go: %v\n", err)
			break
		}
		if changed {
			// 在模块图稳定之前，不要解决缺少的导入。
			// 如果根仍在变化，它们可能会指定
			// 对缺少的包的要求，我们宁愿使用
			// 由新根目录指定的版本，而不是在
			// 无关版本。
			continue
		}

		if !ld.ResolveMissingImports || (!HasModRoot() && !allowMissingModuleImports) {
			// 在不解决缺少的导入的情况下，我们已经加载了尽可能多的内容。
			break
		}

		modAddedBy := ld.resolveMissingImports(ctx)
		if len(modAddedBy) == 0 {
			// 根是稳定的，我们已经解决了所有丢失的包
			// 我们可以。
			break
		}

		toAdd := make([]module.Version, 0, len(modAddedBy))
		for m, _ := range modAddedBy {
			toAdd = append(toAdd, m)
		}
		module.Sort(toAdd) // 犯错误

		// 在解决缺少的导入之前，我们运行了更新要求，但没有
		// 进行任何更改，这样我们就知道需求图已经存在
		// 与ld.pkgs一致：我们不需要将ld.pkgs传递给updateRoots
		// 再一次（这将浪费时间寻找我们已经做出的改变
		// 应用。）
		var noPkgs []*loadPkg
		// 我们还知道，下一步我们将再次调用updateRequirements
		// 所以我们不需要在这里更新它。（那会浪费时间
		// 计算一个“直接”映射，我们以后必须重新计算。）
		direct := ld.requirements.direct
		rs, err := updateRoots(ctx, direct, ld.requirements, noPkgs, toAdd, ld.AssumeRootsImported)
		if err != nil {
			// 如果在新添加的模块中发现错误，请报告该包
			// 导入堆栈而不是模块需求堆栈。包装
			// 更具描述性。
			if err, ok := err.(*mvs.BuildListError); ok {
				if pkg := modAddedBy[err.Module()]; pkg != nil {
					ld.errorf("go: %s: %v\n", pkg.stackText(), err.Err)
					break
				}
			}
			ld.errorf("go: %v\n", err)
			break
		}
		if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
			// 有严重的问题。resolveMissingImports为我们提供了一个非空
			// 要添加到图形中的模块集，但添加这些模块没有
			// 效果-要么它们已经在图中，要么updateRoots没有
			// 按要求添加它们。
			panic(fmt.Sprintf("internal error: adding %v to module graph had no effect on root requirements (%v)", toAdd, rs.rootModules))
		}
		ld.requirements = rs
	}
	base.ExitIfErrors() // 托多（密尔斯）：这真的需要吗？

	// 在我们报告错误之前，整理构建列表（如果适用）。
	// （整理过程可能会从不相关的依赖项中删除错误。）
	if ld.Tidy {
		rs, err := tidyRoots(ctx, ld.requirements, ld.pkgs)
		if err != nil {
			ld.errorf("go: %v\n", err)
		}

		if ld.requirements.depth == lazy {
			// 在装载过程中，我们不断地将整齐的根添加到ld要求中，因此
			// 在这一点上，整齐的根应该是
			// 确保没有新的依赖项被引入
			// 懒惰的加载地平线。
			// 如果不是这样，那么上面的加载循环中就有一个bug。
			for _, m := range rs.rootModules {
				if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
					ld.errorf("go mod tidy: internal error: a requirement on %v is needed but was not added during package loading\n", m)
					base.ExitIfErrors()
				}
			}
		}
		ld.requirements = rs
	}

	// 报告错误（如果有）。
	for _, pkg := range ld.pkgs {
		if pkg.err == nil {
			continue
		}

		// 将导入器信息添加到校验和错误。
		if sumErr := (*ImportMissingSumError)(nil); errors.As(pkg.err, &sumErr) {
			if importer := pkg.stack; importer != nil {
				sumErr.importer = importer.path
				sumErr.importerVersion = importer.mod.Version
				sumErr.importerIsTest = importer.testOf != nil
			}
		}

		if ld.SilencePackageErrors {
			continue
		}
		if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) &&
			stdErr.isStd && ld.SilenceMissingStdImports {
			continue
		}
		if ld.SilenceNoGoErrors && errors.Is(pkg.err, imports.ErrNoGo) {
			continue
		}

		ld.errorf("%s: %v\n", pkg.stackText(), pkg.err)
	}

	ld.checkMultiplePaths()
	return ld
}

// 更新要求确保ld.requirements与
// 从ld.pkgs获得的信息，包括添加为根目录中的模块
// 至少是给定的版本。
// None
// 特别地：
// None
// -提供直接从主模块导入的包的模块包括
// 标记为直接，并提升为显式根。如果需要根
// 由于-mod=readonly或-mod=vendor，无法升级
// 包被标记为错误。
// None
// -如果ld独立于构建约束扫描“all”模式，则为
// 保证看过每一次直接进口。模块依赖关系，但没有
// 不提供任何直接导入的包则标记为间接。
// None
// -根依赖项将更新为其所选版本。
// None
// “更改”返回值报告更新是否更改了所选的
// 提供已加载包的任何模块的版本，或者现在可能
// 提供以前未解析的包。
func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err error) {
	rs := ld.requirements

	// direct包含据信可直接提供软件包的模块集
	// 由主模块导入。
	var direct map[string]bool

	// 如果我们没有扫描主模块的所有导入，或者没有使用
	// imports.AnyTags，那么我们不必加载每个
	// 贡献“直接”导入-因此我们无法安全地标记现有的直接导入
	// ld中的依赖项仅作为间接需求。直接传播它们。
	loadedDirect := ld.allPatternIsRoot && reflect.DeepEqual(ld.Tags, imports.AnyTags())
	if loadedDirect {
		direct = make(map[string]bool)
	} else {
		// TODO（bcmills）：当需要时，在这里分配和复制地图似乎是一种耻辱
		// 它实际上很少与rs.direct不同。衡量这一成本和成本
		// 也许要避免复制。
		direct = make(map[string]bool, len(rs.direct))
		for mPath := range rs.direct {
			direct[mPath] = true
		}
	}

	for _, pkg := range ld.pkgs {
		if pkg.mod != Target {
			continue
		}
		for _, dep := range pkg.imports {
			if !dep.fromExternalModule() {
				continue
			}

			if pkg.err == nil && cfg.BuildMod != "mod" {
				if v, ok := rs.rootSelected(dep.mod.Path); !ok || v != dep.mod.Version {
					// dep.mod不是显式依赖项，但必须是。
					// 因为我们没有处于“mod”模式，我们将无法更新它。
					// 相反，请使用错误标记导入包。
					// None
					// TODO（#41688）：生成的错误消息无法包含该文件
					// 导入语句的位置（因为该信息不是
					// 由模块加载器跟踪）。了解如何垂直导入
					// 定位通过。
					pkg.err = &DirectImportFromImplicitDependencyError{
						ImporterPath: pkg.path,
						ImportedPath: dep.path,
						Module:       dep.mod,
					}
					// cfg.BuildMod不允许我们将dep.mod更改为直接
					// 依赖关系，所以不要将其标记为依赖关系。
					continue
				}
			}

			// dep是由包或测试直接导入的包
			// 模块，并从其他模块（非标准库）加载。
			// 将其模块标记为直接依赖项。
			direct[dep.mod.Path] = true
		}
	}

	var addRoots []module.Version
	if ld.Tidy {
		// 当我们整理一个懒惰的模块时，我们可能需要添加根来保存它
		// 已升级的间接、仅测试依赖项的版本
		// 高于或从direct的go.mod文件中丢失
		// 依赖关系。（例如，直接依赖可能是一个非常复杂的问题
		// 稳定的代码库，早于模块，因此缺少go.mod文件，或
		// 直接依赖项的作者可能忘记提交
		// 更改为go.mod文件，或者可能进行了错误的手动编辑
		// 使其不整洁。）
		// None
		// 将间接依赖项提升到根目录会添加其下一层
		// 对模块图的依赖关系，这可能会增加所选的
		// 我们已经从中加载包的其他模块的版本。
		// 因此，在我们将间接依赖关系提升到根之后，我们需要重新加载
		// 包，这意味着加载的另一个迭代。
		// None
		// 作为一个额外的折痕，由于升级根目录而导致的升级可能会导致
		// 以前解析的包将变为未解析。例如
		// 提供不稳定包的模块可能升级到一个版本
		// 不再包含该包。如果我们解决了失踪的人
		// 包中，我们可能会添加另一个根，以升级其他根
		// 附属国。（mod_tidy_convergence*.txt中的测试说明了一些
		// 尤其令人担忧的情况。）
		// None
		// 确保这一推广、添加和升级过程
		// 最终终止，在迭代过程中，我们只向
		// 根集-我们只在根集的最末端删除不相关的根
		// 迭代，在我们已经添加了我们计划需要的每个根之后
		// 在（最终）整洁的根集中。
		// None
		// 因为我们在迭代过程中不删除任何根，即使它们没有
		// 不再提供任何导入的软件包，所选版本的
		// 根只能增加，根集只能扩大。布景
		// 现有根路径的数量是有限的，每个路径的版本集是有限的
		// 有限，所以迭代*必须*到达一个稳定的不动点。
		tidy, err := tidyRoots(ctx, rs, ld.pkgs)
		if err != nil {
			return false, err
		}
		addRoots = tidy.rootModules
	}

	rs, err = updateRoots(ctx, direct, rs, ld.pkgs, addRoots, ld.AssumeRootsImported)
	if err != nil {
		// 我们甚至不知道根需求应该是什么，
		// 所以我们不能继续装货。将错误返回给调用者
		return false, err
	}

	if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
		// 模块图的根以某种方式发生了变化（不仅仅是
		// “直接”标记）。检查更改是否影响了任何已加载的
		// 包装。
		mg, err := rs.Graph(ctx)
		if err != nil {
			return false, err
		}
		for _, pkg := range ld.pkgs {
			if pkg.fromExternalModule() && mg.Selected(pkg.mod.Path) != pkg.mod.Version {
				changed = true
				break
			}
			if pkg.err != nil {
				// 将模块升级到根目录可能会解析已删除的导入
				// 以前缺少（通过拉入以前删除的依赖项
				// 提供）或模棱两可（仅升级
				// 根的备选方案，忽略第二级备选方案）或
				// 否则出错（通过从无法更新的版本升级）
				// 取到一个可以的）。
				// None
				// 与其列举所有可能的错误，我们只需检查
				// importFromModules是否为包返回nil。
				// 假阳性是可以的：如果我们这里有假阳性，我们会做一个测试
				// 这次是额外的包加载迭代，但我们仍将
				// 当根集停止更改时收敛。
				// None
				// 从某种意义上说，我们可以将其视为“升级模块”
				// pkg.path从“无”到高于“无”的版本。
				if _, _, err = importFromModules(ctx, pkg.path, rs, nil); err == nil {
					changed = true
					break
				}
			}
		}
	}

	ld.requirements = rs
	return changed, nil
}

// resolveMissingImports返回一组可以添加为的模块
// 依赖项，以解决PKG中缺少的包。
// None
// 新解析的包将添加到addedModuleFor map，并且
// resolveMissingImports返回每个新模块版本到的映射
// 模块将解析的第一个丢失的包。
func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg) {
	type pkgMod struct {
		pkg *loadPkg
		mod *module.Version
	}
	var pkgMods []pkgMod
	for _, pkg := range ld.pkgs {
		if pkg.err == nil {
			continue
		}
		if pkg.isTest() {
			// 如果我们遗漏了一个测试，那么我们也遗漏了它的非测试版本，并且
			// 我们应该只添加一次缺少的导入。
			continue
		}
		if !errors.As(pkg.err, new(*ImportMissingError)) {
			// 将其他错误留给Import或load.Packages报告。
			continue
		}

		pkg := pkg
		var mod module.Version
		ld.work.Add(func() {
			var err error
			mod, err = queryImport(ctx, pkg.path, ld.requirements)
			if err != nil {
				// pkg.err已经是非nil，因此我们可以合理地将该错误归为属性
				// 将pkg设置为原始错误或
				// queryImport。现有错误表明我们无法找到
				// 包，而查询错误也解释了为什么我们没有修复
				// 问题是——所以我们更喜欢后者。
				pkg.err = err
			}

			// err为nil，但我们故意将pkg.err保留为non-nil和pkg.mod
			// unset：我们仍然没有满足a的其他不变量
			// 已成功加载包，例如扫描和加载导入
			// 那个包裹的包装。如果我们成功地解决了新的依赖关系图，
			// 调用者可以重新加载pkg并在此时更新错误。
			// None
			// 即使如此，该包也可能无法从我们提供的版本加载
			// 在这里确认。该模块可以通过其他依赖项进行升级，
			// 或者通过mod本身的可传递依赖，或者（不太可能）通过
			// 包可能被AllowPackage钩子拒绝或呈现不明确
			// 通过其他一些新添加或新升级的依赖项。
		})

		pkgMods = append(pkgMods, pkgMod{pkg: pkg, mod: &mod})
	}
	<-ld.work.Idle()

	modAddedBy = map[module.Version]*loadPkg{}
	for _, pm := range pkgMods {
		pkg, mod := pm.pkg, *pm.mod
		if mod.Path == "" {
			continue
		}

		fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, mod.Path, mod.Version)
		if modAddedBy[mod] == nil {
			modAddedBy[mod] = pkg
		}
	}

	return modAddedBy
}

// pkg为路径找到*loadPkg，创建并排队等待加载（如果需要）
// 并更新其状态以反映给定的标志。
// None
// 返回的*loadPkg的导入将异步加载到
// ld.work队列，其测试（如果请求）也将填充一次
// 进口问题已经解决。当ld.work空闲时，所有可传递的
// 请求的包（及其测试，如果请求）将已加载。
func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loadPkg {
	if flags.has(pkgImportsLoaded) {
		panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set")
	}

	pkg := ld.pkgCache.Do(path, func() interface{} {
		pkg := &loadPkg{
			path: path,
		}
		ld.applyPkgFlags(ctx, pkg, flags)

		ld.work.Add(func() { ld.load(ctx, pkg) })
		return pkg
	}).(*loadPkg)

	ld.applyPkgFlags(ctx, pkg, flags)
	return pkg
}

// applyPkgFlags更新pkg.flags以设置给定标志并传播
// （传递性）这些标志的影响，可能进一步加载或排队
// 因此，包。
func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkgFlags) {
	if flags == 0 {
		return
	}

	if flags.has(pkgInAll) && ld.allPatternIsRoot && !pkg.isTest() {
		// 由于处于“all”状态，此包与根模式匹配。
		flags |= pkgIsRoot
	}
	if flags.has(pkgIsRoot) {
		flags |= pkgFromRoot
	}

	old := pkg.flags.update(flags)
	new := old | flags
	if new == old || !new.has(pkgImportsLoaded) {
		// 我们要么没有改变pkg的状态，要么对pkg一无所知
		// 它的依赖性还没有确定。无论如何，我们都无法有效地加载其测试或
		// 更新其依赖项。
		return
	}

	if !pkg.isTest() {
		// 检查是否应该为pkg添加测试（或更新标记）。
		// ld.pkgTest是幂等的，额外的调用不太昂贵，
		// 因此，如果我们称之为超出严格必要的范围，也没关系。
		wantTest := false
		switch {
		case ld.allPatternIsRoot && pkg.mod == Target:
			// 我们正在加载“all”模式，其中包括由导入的包
			// 在主模块中进行测试。这个包在主模块中，所以我们
			// 即使未设置LoadTests，也需要标识其测试的导入。
			// None
			// （我们将在computePatternAll中明确过滤掉额外的测试。）
			wantTest = true

		case ld.allPatternIsRoot && ld.allClosesOverTests && new.has(pkgInAll):
			// “all”模式的这个变体包括每个
			// 包本身在“all”中，而pkg在“all”中，所以它的测试是
			// 也在“全部”（如上所述）中。
			wantTest = true

		case ld.LoadTests && new.has(pkgIsRoot):
			// LoadTest显式请求对“根包”进行测试。
			wantTest = true
		}

		if wantTest {
			var testFlags loadPkgFlags
			if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
				// 主模块中的包测试为“全部”，即
				// 它们导致它们导入的包也处于“全部”状态。测试也是如此
				// 如果“全部”关闭测试依赖项，则“全部”中的包数。
				testFlags |= pkgInAll
			}
			ld.pkgTest(ctx, pkg, testFlags)
		}
	}

	if new.has(pkgInAll) && !old.has(pkgInAll|pkgImportsLoaded) {
		// 我们刚刚用pkgInAll标记了pkg，或者我们刚刚加载了它的
		// 进口，或两者兼而有之。现在是将pkgInAll传播到导入的时候了。
		for _, dep := range pkg.imports {
			ld.applyPkgFlags(ctx, dep, pkgInAll)
		}
	}

	if new.has(pkgFromRoot) && !old.has(pkgFromRoot|pkgImportsLoaded) {
		for _, dep := range pkg.imports {
			ld.applyPkgFlags(ctx, dep, pkgFromRoot)
		}
	}
}

// preloadRootModules加载标识所需的模块需求
// 在rootPkgs中提供包的每个模块的选定版本，
// 如果需要，将新的根模块添加到模块图中。
func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (changedBuildList bool) {
	needc := make(chan map[module.Version]bool, 1)
	needc <- map[module.Version]bool{}
	for _, path := range rootPkgs {
		path := path
		ld.work.Add(func() {
			// 首先，尝试仅使用根标识包含包的模块。
			// None
			// 如果主模块整洁且包装为“全部”-或者如果我们
			// 幸运的是，我们可以识别其所有进口产品，而无需实际加载
			// 全模图。
			m, _, err := importFromModules(ctx, path, ld.requirements, nil)
			if err != nil {
				var missing *ImportMissingError
				if errors.As(err, &missing) && ld.ResolveMissingImports {
					// 此软件包不是由任何选定的模块提供的。
					// 如果我们能找到它，它将是一个新的根依赖项。
					m, err = queryImport(ctx, path, ld.requirements)
				}
				if err != nil {
					// 我们无法识别包含此包的根模块。
					// 悬而未决；我们将在装货时报告。
					return
				}
			}
			if m.Path == "" {
				// 该软件包采用std或cmd格式。我们不需要更改根集。
				return
			}

			v, ok := ld.requirements.rootSelected(m.Path)
			if !ok || v != m.Version {
				// 我们在m中找到了请求的包，但是m不是根，所以
				// loadModGraph不会加载其需求。我们需要促进这一进程
				// 将模块发送到根目录，以确保任何其他包都可以访问此包
				// 从正确的依赖项版本解析导入。
				// None
				// （这是惰性加载设计中的“参数不变量”。）
				need := <-needc
				need[m] = true
				needc <- need
			}
		})
	}
	<-ld.work.Idle()

	need := <-needc
	if len(need) == 0 {
		return false // 没有根可以添加。
	}

	toAdd := make([]module.Version, 0, len(need))
	for m := range need {
		toAdd = append(toAdd, m)
	}
	module.Sort(toAdd)

	rs, err := updateRoots(ctx, ld.requirements.direct, ld.requirements, nil, toAdd, ld.AssumeRootsImported)
	if err != nil {
		// 我们缺少一些根依赖项，由于某些原因无法加载
		// 足够的模块依赖关系图来添加缺少的根。包裹
		// 加载注定会失败，所以很快就会失败。
		ld.errorf("go: %v\n", err)
		base.ExitIfErrors()
		return false
	}
	if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
		// 有严重的问题。resolveMissingImports为我们提供了一个非空
		// 要添加到图形中的模块集，但添加这些模块没有
		// 效果-要么它们已经在图中，要么updateRoots没有
		// 按要求添加它们。
		panic(fmt.Sprintf("internal error: adding %v to module graph had no effect on root requirements (%v)", toAdd, rs.rootModules))
	}

	ld.requirements = rs
	return true
}

// 加载单个包。
func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
	if strings.Contains(pkg.path, "@") {
		// 在加载过程中留下错误。
		return
	}
	if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
		// 在加载过程中留下错误。
		// （模块模式不允许本地导入。）
		return
	}

	if search.IsMetaPackage(pkg.path) {
		pkg.err = &invalidImportError{
			importPath: pkg.path,
			err:        fmt.Errorf("%q is not an importable package; see 'go help packages'", pkg.path),
		}
		return
	}

	var mg *ModuleGraph
	if ld.requirements.depth == eager {
		var err error
		mg, err = ld.requirements.Graph(ctx)
		if err != nil {
			// 我们已经在loadFromRoots和/或中检查了图表中的错误
			// 更新要求，所以我们故意忽略了错误，应该
			// 继续努力超越它。
			// None
			// 但是，由于mg可能不完整（因此可能选择不准确
			// 版本），我们不应该使用它来加载包。相反，我们通过了零分
			// *ModuleGraph，这将导致mg首先尝试仅从
			// 主模块和根依赖项。
			mg = nil
		}
	}

	pkg.mod, pkg.dir, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg)
	if pkg.dir == "" {
		return
	}
	if pkg.mod == Target {
		// 继续，将pkg标记为“全部”。这提供了
		// 在“all”中由其他包*仅*导入的包始终为
		// 在加载其导入之前标记为这样。
		// None
		// 我们目前实际上并不依赖于这个不变量，但它可能
		// 稍微提高效率，使行为更易于推理
		// 关于（通过减少依赖项的标记位的搅动）和成本
		// 基本上什么都没有（这些原子旗子行动基本上是免费的
		// 扫描源代码以进行导入）。
		ld.applyPkgFlags(ctx, pkg, pkgInAll)
	}
	if ld.AllowPackage != nil {
		if err := ld.AllowPackage(ctx, pkg.path, pkg.mod); err != nil {
			pkg.err = err
		}
	}

	pkg.inStd = (search.IsStandardImportPath(pkg.path) && search.InDir(pkg.dir, cfg.GOROOTsrc) != "")

	var imports, testImports []string

	if cfg.BuildContext.Compiler == "gccgo" && pkg.inStd {
		// 我们无法扫描gccgo的标准包。
	} else {
		var err error
		imports, testImports, err = scanDir(pkg.dir, ld.Tags)
		if err != nil {
			pkg.err = err
			return
		}
	}

	pkg.imports = make([]*loadPkg, 0, len(imports))
	var importFlags loadPkgFlags
	if pkg.flags.has(pkgInAll) {
		importFlags = pkgInAll
	}
	for _, path := range imports {
		if pkg.inStd {
			// 从“std”和“cmd”中的包导入应使用
			// GOROOT/src/供应商，即使“std”不是主模块。
			path = ld.stdVendor(pkg.path, path)
		}
		pkg.imports = append(pkg.imports, ld.pkg(ctx, path, importFlags))
	}
	pkg.testImports = testImports

	ld.applyPkgFlags(ctx, pkg, pkgImportsLoaded)
}

// pkgTest定位pkg的测试，根据需要创建它，并更新其状态
// 以反映给定的标志。
// None
// pkgTest要求已加载（标记）pkg的导入
// 使用pkgImportsLoaded）。
func (ld *loader) pkgTest(ctx context.Context, pkg *loadPkg, testFlags loadPkgFlags) *loadPkg {
	if pkg.isTest() {
		panic("pkgTest called on a test package")
	}

	createdTest := false
	pkg.testOnce.Do(func() {
		pkg.test = &loadPkg{
			path:   pkg.path,
			testOf: pkg,
			mod:    pkg.mod,
			dir:    pkg.dir,
			err:    pkg.err,
			inStd:  pkg.inStd,
		}
		ld.applyPkgFlags(ctx, pkg.test, testFlags)
		createdTest = true
	})

	test := pkg.test
	if createdTest {
		test.imports = make([]*loadPkg, 0, len(pkg.testImports))
		var importFlags loadPkgFlags
		if test.flags.has(pkgInAll) {
			importFlags = pkgInAll
		}
		for _, path := range pkg.testImports {
			if pkg.inStd {
				path = ld.stdVendor(test.path, path)
			}
			test.imports = append(test.imports, ld.pkg(ctx, path, importFlags))
		}
		pkg.testImports = nil
		ld.applyPkgFlags(ctx, test, pkgImportsLoaded)
	} else {
		ld.applyPkgFlags(ctx, test, testFlags)
	}

	return test
}

// stdVendor返回具有给定
// 从parentPath处的标准库包导入时的路径。
func (ld *loader) stdVendor(parentPath, path string) string {
	if search.IsStandardImportPath(path) {
		return path
	}

	if str.HasPathPrefix(parentPath, "cmd") {
		if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" {
			vendorPath := pathpkg.Join("cmd", "vendor", path)
			if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
				return vendorPath
			}
		}
	} else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
		// 如果我们在“std”模块之外，请解析“std”模块内的导入
		// 到供应商目录。
		// None
		// 对于以前缀“vendor/”开头的进口商，即使我们
		// 是否在“std”模块的*内部：解析的“vendor/”包
		// 从GOROOT/src/vendor全局（并作为“go list std”的一部分列出）
		// 与实际模块依赖项不同，并且无法导入
		// 来自真实模块的内部包。
		// None
		// （请注意，尽管“供应商/”软件包与“标准”软件包相匹配*
		// 模式，它们不是std*模块*的一部分，不会影响
		// 在标准范围内工作时，“go mod tidy”和类似的模块命令。）
		vendorPath := pathpkg.Join("vendor", path)
		if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
			return vendorPath
		}
	}

	// 非供应商：从模块解析。
	return path
}

// computePatternAll返回与模式“all”匹配的包列表，
// 从主模块中包的导入路径列表开始。
func (ld *loader) computePatternAll() (all []string) {
	for _, pkg := range ld.pkgs {
		if pkg.flags.has(pkgInAll) && !pkg.isTest() {
			all = append(all, pkg.path)
		}
	}
	sort.Strings(all)
	return all
}

// CheckMultiplePath验证给定模块路径是否用作其自身
// 或者作为另一个模块的替代品，但不能同时使用这两个模块。
// None
// （见https:
func (ld *loader) checkMultiplePaths() {
	mods := ld.requirements.rootModules
	if cached := ld.requirements.graph.Load(); cached != nil {
		if mg := cached.(cachedGraph).mg; mg != nil {
			mods = mg.BuildList()
		}
	}

	firstPath := map[module.Version]string{}
	for _, mod := range mods {
		src := resolveReplacement(mod)
		if prev, ok := firstPath[src]; !ok {
			firstPath[src] = mod.Path
		} else if prev != mod.Path {
			ld.errorf("go: %s@%s used for two different module paths (%s and %s)\n", src.Path, src.Version, prev, mod.Path)
		}
	}
}

// checkTidyCompatibility在任何包将从数据库加载时发出错误
// rs下的模块与ld下的模块不同。
func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements) {
	suggestUpgrade := false
	suggestEFlag := false
	suggestFixes := func() {
		if ld.AllowErrors {
			// 用户明确地忽略了这些错误，所以不必为它们操心
			// 其他选择。
			return
		}

		// 我们直接打印到os.Stderr，因为这些信息是关于
		// 如何修复错误，而不是错误本身。
		// （实际的错误应该已经记录了。）

		fmt.Fprintln(os.Stderr)

		goFlag := ""
		if ld.GoVersion != modFileGoVersion() {
			goFlag = " -go=" + ld.GoVersion
		}

		compatFlag := ""
		if ld.TidyCompatibleVersion != priorGoVersion(ld.GoVersion) {
			compatFlag = " -compat=" + ld.TidyCompatibleVersion
		}
		if suggestUpgrade {
			eDesc := ""
			eFlag := ""
			if suggestEFlag {
				eDesc = ", leaving some packages unresolved"
				eFlag = " -e"
			}
			fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", ld.TidyCompatibleVersion, eDesc, eFlag, ld.TidyCompatibleVersion, eFlag, ld.GoVersion, compatFlag)
		} else if suggestEFlag {
			// 如果缺少一些包，但没有升级包，则我们
			// 不应该明确建议升级到Go 1.16版本-这
			// 实际上不会为Go 1.16用户修复任何东西，而且会崩溃
			// Go 1.17用户的东西。
			fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", ld.TidyCompatibleVersion, goFlag, compatFlag)
		}

		fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", ld.TidyCompatibleVersion, goFlag, ld.GoVersion)

		// TODO（#46141）：填充链接的wiki页面。
		fmt.Fprintf(os.Stderr, "For other options, see:\n\thttps:// golang.org/doc/modules/prunning\n“
	}

	mg, err := rs.Graph(ctx)
	if err != nil {
		ld.errorf("go mod tidy: error loading go %s module graph: %v\n", ld.TidyCompatibleVersion, err)
		suggestFixes()
		return
	}

	// 并行地重新解析包。
	// None
	// 我们重新解析每个包，而不仅仅是检查版本，以确保
	// 我们已经获取了模块源代码（重要的是，还有
	// 该源代码）用于确保导入所需的所有模块
	// 它们是明确的。这也产生了更清晰的诊断，因为我们可以说
	// 如果包裹变得模棱两可或消失了，它到底发生了什么
	// 完全
	// None
	// 我们并行地重新解析包，因为这个过程涉及磁盘
	// 检查包源的I/O，以及检查
	// 模棱两可的导入可能需要我们下载额外的
	// 否则在Go 1.17中删减-我们不想阻止其他方面的进展
	// 当我们等待一个新的下载时。
	type mismatch struct {
		mod module.Version
		err error
	}
	mismatchMu := make(chan map[*loadPkg]mismatch, 1)
	mismatchMu <- map[*loadPkg]mismatch{}
	for _, pkg := range ld.pkgs {
		if pkg.mod.Path == "" && pkg.err == nil {
			// 此软件包来自标准库（标准库不随时间变化）
			// 模块图）。
			continue
		}

		pkg := pkg
		ld.work.Add(func() {
			mod, _, err := importFromModules(ctx, pkg.path, rs, mg)
			if mod != pkg.mod {
				mismatches := <-mismatchMu
				mismatches[pkg] = mismatch{mod: mod, err: err}
				mismatchMu <- mismatches
			}
		})
	}
	<-ld.work.Idle()

	mismatches := <-mismatchMu
	if len(mismatches) == 0 {
		// 因为我们是作为“go mod tidy”的一部分运行的，所以模块的根
		// 图表应仅包含与中的某些包相关的模块
		// 包图。我们检查了包图中的每个包，并
		// 没有发现任何不匹配，所以这一定意味着
		// 模块图也是一致的。
		// None
		// 如果我们错了，进入1.16 in-mod=readonly模式将出错
		// “需要更新go.mod”，这会让人非常困惑。所以相反，
		// 我们将再次检查上述推理是否正确——如果正确的话
		// 否则，我们将发出一个内部错误，希望用户能够报告
		// 这是一个错误。
		for _, m := range ld.requirements.rootModules {
			if v := mg.Selected(m.Path); v != m.Version {
				fmt.Fprintln(os.Stderr)
				base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https:// golang.org/issue.，，m.Path，ld.GoVersion，m.Version，ld.tidycompatiblevision，v）
			}
		}
		return
	}

	// 迭代包（而不是不匹配映射）以在
	// 确定性秩序。
	for _, pkg := range ld.pkgs {
		mismatch, ok := mismatches[pkg]
		if !ok {
			continue
		}

		if pkg.isTest() {
			// 我们已经（或将）报告程序包本身的错误，
			// 因此，不要为其测试报告重复（和更多vebose）错误。
			if _, ok := mismatches[pkg.testOf]; !ok {
				base.Fatalf("go: internal error: mismatch recorded for test %s, but not its non-test package", pkg.path)
			}
			continue
		}

		switch {
		case mismatch.err != nil:
			// pkg已成功解决，但使用rs中的要求排除了错误。
			// None
			// 这可能是因为导入是由单个惰性根提供的
			// （因此在惰性模式下是明确的）以及一个或多个
			// 可传递依赖项（在急切模式下是不明确的）。
			// None
			// 这也可能是因为某些可传递的依赖项升级了
			// 以前将包提供给版本为
			// 更长的版本，或模块源代码（但
			// 不是单独的go.mod文件）有校验和错误。
			if missing := (*ImportMissingError)(nil); errors.As(mismatch.err, &missing) {
				selected := module.Version{
					Path:    pkg.mod.Path,
					Version: mg.Selected(pkg.mod.Path),
				}
				ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, selected)
			} else {
				if ambiguous := (*AmbiguousImportError)(nil); errors.As(mismatch.err, &ambiguous) {
					// 托多：需要这张支票吗？
				}
				ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.err)
			}

			suggestEFlag = true

			// 即使我们继续使用“-e”标志，旧版本也会继续使用
			// 如果认为go.mod文件包含
			// 不在所选版本的任何*显式*依赖项，
			// 即使该依赖关系与正在加载的任何包无关。
			// None
			// 我们在这里检查这种情况。如果所有的根都是一致的
			// “-e”标志就足够了，但除此之外，我们需要建议升级。
			if !suggestUpgrade {
				for _, m := range ld.requirements.rootModules {
					if v := mg.Selected(m.Path); v != m.Version {
						suggestUpgrade = true
						break
					}
				}
			}

		case pkg.err != nil:
			// pkg在惰性模式下出错（可能被-e标志抑制），
			// 但不是在急切的状态下。
			// None
			// 这是可能的，如果导入在惰性模式下无法解析
			// （因为每个候选模块的“最新”版本
			// 不可用或不包含包），但已在中解析
			// 由于比最新的依赖项（通常为
			// 超出模块图的范围。
			// None
			// 如果提供
			// 惰性模式下的包有校验和错误，但急切模式升级
			// 将该模块转换为具有正确校验和的版本。
			// None
			// pkg.err应该已经记录在其他地方-以及
			// 堆栈跟踪-因此在此处仅记录导入路径和非错误信息。
			suggestUpgrade = true
			ld.errorf("%s failed to load from any module,\n\tbut go %s would load it from %v\n", pkg.path, ld.TidyCompatibleVersion, mismatch.mod)

		case pkg.mod != mismatch.mod:
			// 两个Go版本都成功加载了该包，但都是从
			// 每个模块中有不同的模块。这可能会导致微妙的（甚至是
			// 不被注意！）不同版本的构建之间的行为差异
			// 工具链。
			suggestUpgrade = true
			ld.errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.mod.Version)

		default:
			base.Fatalf("go: internal error: mismatch recorded for package %s, but no differences found", pkg.path)
		}
	}

	suggestFixes()
	base.ExitIfErrors()
}

// scanDir与imports类似。scanDir将已知的魔法导入从列表中删除，
// 这样我们就不会去寻找那些根本不存在的包。
// None
// 对于cgo，标准的magic导入是“C”。
// None
// 唯一已知的其他魔术导入是appengine和appengine/*。
// 这些路径非常古老，它们早于“go-get”，并且没有使用类似URL的路径。
// 现在大多数代码都使用google.golang.org/appengine，
// 但并非所有代码都已如此更新。当我们忽略构建标记时
// 在“go vendor”期间，我们调查
// 可能会看到这些遗留导入。我们放下它们，这样模块
// 搜索不寻找模块来满足它们。
func scanDir(dir string, tags map[string]bool) (imports_, testImports []string, err error) {
	imports_, testImports, err = imports.ScanDir(dir, tags)

	filter := func(x []string) []string {
		w := 0
		for _, pkg := range x {
			if pkg != "C" && pkg != "appengine" && !strings.HasPrefix(pkg, "appengine/") &&
				pkg != "appengine_internal" && !strings.HasPrefix(pkg, "appengine_internal/") {
				x[w] = pkg
				w++
			}
		}
		return x[:w]
	}

	return filter(imports_), filter(testImports), err
}

// buildStacks计算每个包的最小导入堆栈，
// 用于错误消息中。当它完成时，包
// 是原始根集的一部分，且pkg.stack==nil，
// 其他包的pkg.stack指向下一个
// 将导入堆栈打包到它们的最小链中。
// 作为一个副作用，buildStacks还构建ld.pkgs，
// 已加载的所有包的列表。
func (ld *loader) buildStacks() {
	if len(ld.pkgs) > 0 {
		panic("buildStacks")
	}
	for _, pkg := range ld.roots {
		pkg.stack = pkg // sentinel避免在下一个循环中处理
		ld.pkgs = append(ld.pkgs, pkg)
	}
	for i := 0; i < len(ld.pkgs); i++ { // 非范围：追加到循环中的ld.pkgs
		pkg := ld.pkgs[i]
		for _, next := range pkg.imports {
			if next.stack == nil {
				next.stack = pkg
				ld.pkgs = append(ld.pkgs, next)
			}
		}
		if next := pkg.test; next != nil && next.stack == nil {
			next.stack = pkg
			ld.pkgs = append(ld.pkgs, next)
		}
	}
	for _, pkg := range ld.roots {
		pkg.stack = nil
	}
}

// stackText生成导入堆栈文本以在
// 报告pkg中的错误。它有一般形式
// None
// 根导入
// 其他进口
// 其他2由
// 其他2.测试导入
// 包装
// None
func (pkg *loadPkg) stackText() string {
	var stack []*loadPkg
	for p := pkg; p != nil; p = p.stack {
		stack = append(stack, p)
	}

	var buf bytes.Buffer
	for i := len(stack) - 1; i >= 0; i-- {
		p := stack[i]
		fmt.Fprint(&buf, p.path)
		if p.testOf != nil {
			fmt.Fprint(&buf, ".test")
		}
		if i > 0 {
			if stack[i-1].testOf == p {
				fmt.Fprint(&buf, " tested by\n\t")
			} else {
				fmt.Fprint(&buf, " imports\n\t")
			}
		}
	}
	return buf.String()
}

// why返回要在关于给定包的“go mod why”输出中使用的文本。
// 它没有stackText华丽，但包含相同的信息。
func (pkg *loadPkg) why() string {
	var buf strings.Builder
	var stack []*loadPkg
	for p := pkg; p != nil; p = p.stack {
		stack = append(stack, p)
	}

	for i := len(stack) - 1; i >= 0; i-- {
		p := stack[i]
		if p.testOf != nil {
			fmt.Fprintf(&buf, "%s.test\n", p.testOf.path)
		} else {
			fmt.Fprintf(&buf, "%s\n", p.path)
		}
	}
	return buf.String()
}

// Why返回给定包的“go mod Why”输出节，
// 没有领先的评论。
// 包图必须已经加载，通常由LoadPackages加载。
// 如果包没有理由位于当前生成中，
// 为什么返回空字符串。
func Why(path string) string {
	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
	if !ok {
		return ""
	}
	return pkg.why()
}

// WhyDepth返回Why列表中的步骤数。
// 如果包没有理由位于当前生成中，
// WhyDepth返回0。
func WhyDepth(path string) int {
	n := 0
	pkg, _ := loaded.pkgCache.Get(path).(*loadPkg)
	for p := pkg; p != nil; p = p.stack {
		n++
	}
	return n
}
